# coding: utf-8
import os
import sys
import logging
import pygame
import random
import time
import imp

if (hasattr(sys, "frozen") # new py2exe
        or hasattr(sys, "importers") # old py2exe
        or imp.is_frozen("__main__")):
    rootdir = os.path.dirname(sys.executable)
else:
    rootdir = os.path.dirname(sys.argv[0])

try:
    __file__    # note py2exe can have __file__. refer into the `.exe` file
except NameError:
    insertdir = rootdir
else:
    insertdir = os.path.dirname(os.path.dirname(__file__))

logger = logging.getLogger('wordson.game')
logger.debug('rootdir=%s; insertdir=%s', rootdir, insertdir)

sys.path.insert(0, insertdir)
from lib.bashlog import getlogger
from lib.tracemore import get_exc_plus
from lib.config import Config
from lib.data import Data
from lib.minsix import Str
from lib import color
from lib import events
from lib.mainpage import MainPage
from lib.scrollpage import LanguagePage
from lib.scrollpage import CategoryPage
from lib.scrollpage import LibPage
from lib.gamepage import GamePage
from lib.modepage import ModePage
from lib.endpage import EndPage
sys.path.pop(0)


cfg = Config()
data = Data()

class Game(object):
    '''The Controller of the game'''
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode(cfg.screen)

        self._main_page = MainPage()
        self.lang_page = LanguagePage(data.get_langs())
        self.mode_page = ModePage()
        self.load_page = None
        self.game_page = GamePage()

        self.current_page = self.main_page

        self.running = True

        self.all_source = {}    # store the all data.
        self.rest_source = {}    # store the untested data.
        self.wrong = {}    # store all wrong answer. format: {'word': int}
        self.current_word = None    # current word for the topic
        self.current_answer = None

    @property
    def main_page(self):
        page = self._main_page
        saved_data = data.save_data
        if saved_data is not None:
            saved_time = self.format_time(saved_data['time'])
            page.set_load_date(saved_time)
        return page

    @property
    def end_page(self):
        total = len(self.all_source)
        right = total - len(self.wrong)
        wrong_and_num = [('/'.join(word), times) for word, times in self.wrong.items()]
        used_time = self.game_page.get_formatted_time()
        return EndPage(used_time, (right, total), wrong_and_num, self.repeat_word)

    def cache_cate(func):
        cached = {}
        def warpper(self):
            return cached.setdefault(self.lang, func(self))
        return warpper
    @property
    @cache_cate
    def cate_page(self):
        return CategoryPage(data.get_cates_under(self.lang))


    def cache_lib(func):
        cached = {}
        def warpper(self):
            return cached.setdefault((self.lang, self.cate), func(self))
        return warpper

    @property
    @cache_lib
    def page_lib(self):
        result = data.get_name_under(self.lang, self.cate)
        return LibPage(result)


    def run(self):

        pygame.key.set_repeat(cfg.key_repeat_delay, cfg.key_repeat_interval)
        pygame.display.set_caption(cfg.title)

        screen = self.screen

        clock = pygame.time.Clock()

        while self.running:
            for event in pygame.event.get():
                if self.current_page.handle(event):
                    continue
                if self.handle(event):
                    continue


            interval = clock.tick(cfg.tick)
            self.current_page.update(interval)
            screen.fill(color.white)
            self.current_page.draw(screen)
            pygame.display.flip()

        pygame.quit()

    def handle(self, event):
        if event.type == pygame.QUIT:
            logger.info('exit...')
            self.running = False
            return True
        if event.type == events.SWITCHPAGE:
            to = event.to
            logger.debug('switch page %s', event)
            if to == cfg.page_main:
                logger.info('to main page')
                self.current_page = self.main_page
                # if hasattr(self, 'lang'):
                #     del self.lang
            elif to == cfg.page_lang:
                logger.info('to language page')
                self.current_page = self.lang_page
                if hasattr(self, 'lang'):
                    del self.lang
            elif to == cfg.page_cate:
                logger.info('to category page')
                if event.by == cfg.page_lang:
                    self.lang = event.lang
                self.current_page = self.cate_page
            elif to == cfg.page_lib:
                logger.info('to lib page')
                if event.by == cfg.page_cate:
                    self.cate = event.cate
                self.current_page = self.page_lib
            elif to == cfg.page_mode:
                logger.info('to mode page')
                if event.by == cfg.page_lib:
                    self.lib = event.lib
                self.current_page = self.mode_page
            elif to == cfg.page_load:
                self.current_page = self.load_page
            elif to == cfg.page_game:
                self.show_type = event.show_type
                self.show_1st_char = event.show_1st_char
                self.repeat_word = event.repeat

                self.write_word = event.write_word
                self.choose_word = event.choose_word
                self.choose_meaning = event.choose_meaning
                lang = self.lang
                cate = self.cate
                lib  = self.lib
                logger.info('to game page [%s - %s - %s]; '
                            'write_word: %s, choose_word: %s, choose_meaning: %s; '
                            'show_type, %s; show_1st_char %s; repeat: %s',
                            lang, cate, lib,
                            self.write_word, self.choose_word, self.choose_meaning,
                            self.show_type, self.show_1st_char, self.repeat_word)

                self.game_page.clear()
                self.load_data(lang, cate, lib)
                self.current_page = self.game_page
            elif to == cfg.page_end:
                self.current_page = self.end_page
            logger.debug('event %s handle by game', event)
            return True

        # required to check if the answer is correct
        elif event.type == events.ANSWER:
            # answer = event.answer
            self.handle_answer(event.answer)
            return True

        # required to renew the topic
        elif event.type == events.NEXTTOPIC:
            self.set_new_topic()
            self.set_process()
            logger.debug('event %s handled by game', event)
            return True

        # required to save the game
        elif event.type == events.SAVE and not hasattr(event, 'msg'):
            self.save_game()
            logger.debug('save event %s handled by game', event)
            return True

        # required to load the game
        elif event.type == events.LOAD:
            if data.save_data:
                logger.debug('found save')
                self.load_game()
            logger.debug('save event %s handled by game', event)
            return True

    def get_time(self, strtime=None):
        if strtime is None:
            return time.time()
        timestruct = time.strptime(strtime, '%Y-%m-%d %H:%M:%S')
        return time.mktime(timestruct)


    def format_time(self, thetime=None):
        if thetime is None:
            thetime = time.localtime()
        else:
            thetime = time.localtime(thetime)
        return time.strftime('%Y-%m-%d %H:%M:%S', thetime)

    def save_game(self):
        obj = {}
        obj['config'] = {
            'show_type': self.show_type,
            'show_1st_char': self.show_1st_char,
            'repeat': self.repeat_word,
            'write_word': self.write_word,
            'choose_word': self.choose_word,
            'choose_meaning': self.choose_meaning,
        }

        obj['language'] = self.lang
        obj['category'] = self.cate
        obj['lib'] = self.lib
        obj['time'] = self.get_time()
        obj['used_time'] = self.game_page.get_time()

        source = []
        for word, meaning in self.all_source.items():
            source.append(
                {'word': word,
                 'meaning': meaning}
            )
        obj['source'] = source

        wrong = []
        for word, times in self.wrong.items():
            wrong.append(
                {'word': word,
                 'time': times}
            )
        obj['wrong'] = wrong

        rest = []
        for word, _ in self.rest_source.items():
            rest.append(word)
        obj['rest'] = rest

        evt = events.save()
        try:
            data.save_game(obj)
        except BaseException as e:
            msg = str(e)
            logger.error(msg)
        else:
            msg = None
        evt.msg = msg
        logger.debug('post save done event %s', evt)
        pygame.event.post(evt)

    def load_game(self):
        obj = data.load_game()
        self.game_page.set_time(obj['used_time'])
        self.lang = obj['language']
        self.cate = obj['category']
        self.lib = obj['lib']

        all_source = {}
        for each in obj['source']:
            all_source[tuple(each['word'])] = each['meaning']
        self.all_source = all_source

        rest_source = {}
        for word in obj['rest']:
            word = tuple(word)
            rest_source[word] = all_source[word]
        self.rest_source = rest_source

        wrong = {}
        for each in obj['wrong']:
            wrong[tuple(each['word'])] = each['time']
        self.wrong = wrong

        config = obj['config']
        pygame.event.post(events.switchpage(by=cfg.page_main, to=cfg.page_game, **config))

    def handle_answer(self, answer):
        if isinstance(answer, Str):
            answer = answer.lower()

        current = self.current_answer

        result = answer in current
        if isinstance(current[0], Str):
            answer = '; '.join(current)
        else:
            answer = 'ABCD'[current[0] - pygame.K_a]
        evt = events.result(result, answer)
        logger.info('for answer %s, post %s', answer, evt)
        pygame.event.post(evt)

        this_word = self.current_word
        if not result:    # record the error
            self.wrong[this_word] = self.wrong.get(this_word, 0) + 1

        if not self.rest_source:
            return self.switch_end()

        if (not self.repeat_word) or result:
            poped = self.rest_source.pop(this_word)
            logger.info('word poped: %s - %s ', this_word, poped)

    def switch_end(self):
        assert False

    def load_data(self, lang, cate, lib):
        source_data = data.get_under(lang, cate, lib)

        self.all_source.clear()
        self.rest_source.clear()
        self.all_source.update(self.convert_data(source_data))
        self.rest_source.update(self.all_source)

        self.set_new_topic()

    def set_process(self):
        page = self.game_page
        allnum = len(self.all_source)
        restnum = len(self.rest_source)
        result =  (allnum - restnum) * 100 / allnum
        page.set_process(result)
    def set_new_topic(self):

        title, options, placeholder = self.new_topic()

        if title is None:
            logger.warning('empty lib')
            pygame.event.post(events.switchpage(to=cfg.page_end))
            return

        self.game_page.set(title, options, placeholder)


    def new_topic(self):
        if not self.rest_source:
            return None, None, None

        funcs = []
        if self.write_word:
            funcs.append(self.new_write_word)
        if self.choose_word:
            funcs.append(self.new_choose_word)
        if self.choose_meaning:
            funcs.append(self.new_choose_meaning)

        func = random.choice(funcs)
        return func()

    def new_write_word(self):
        '''give meaning and write word'''
        current_word = self.current_word
        rest_source = list(self.rest_source.items())

        answer, raw_title = self.random_except(rest_source, current_word)
        title_strs = self.merge_meaning(raw_title)

        self.current_word = answer
        self.current_answer = answer

        logger.info('word: %s; answer %s', self.current_word, self.current_answer)

        if self.show_1st_char:
            placeholder = answer[0][0]
        else:
            placeholder = cfg.placeholder

        return title_strs, None, placeholder

    def new_choose_word(self):
        '''give meaning and choose word'''
        current_word = self.current_word
        rest_source = list(self.rest_source.items())
        new_word, new_meaning = self.random_except(rest_source, current_word)
        options = [new_word]

        all_source = dict(self.all_source)    # a copy
        # all_source.pop(current_word)
        all_source.pop(new_word)

        if len(all_source) <= 3:
            options.extend(all_source.keys())
        else:
            avaliable = list(all_source.items())
            while len(options) < 4:
                new = random.choice(avaliable)
                avaliable.remove(new)
                options.append(new[0])

        random.shuffle(options)    # note it's in place
        answer = options.index(new_word)
        assert answer != -1, "Not found answer in %s"%options

        self.current_word = new_word
        self.current_answer = (answer+pygame.K_a, )

        logger.info('word: %s; answer %s', new_word, answer)


        return self.merge_meaning(new_meaning, sep='; '), ['; '.join(x) for x in options], None


    def new_choose_meaning(self):
        '''give word choose meaning'''
        current_word = self.current_word
        rest_source = list(self.rest_source.items())
        new_word, new_meaning = self.random_except(rest_source, current_word)
        options = [new_meaning]

        all_source = dict(self.all_source)    # a copy
        # all_source.pop(current_word)
        all_source.pop(new_word)
        if len(all_source) < 4:
            options.extend(all_source.values())
        else:
            avaliable = list(all_source.items())
            while len(options) < 4:
                new = random.choice(avaliable)
                avaliable.remove(new)
                options.append(new[1])

        random.shuffle(options)    # note it's in place
        answer = options.index(new_meaning)
        assert answer != -1, "Not found answer in %s"%options

        self.current_word = new_word
        self.current_answer = (answer + pygame.K_a, )
        logger.info('word: %s; answer %s', new_word, answer)


        return '; '.join(new_word), [self.merge_meaning(x, sep='; ') for x in options], None

    def merge_meaning(self, meaning, sep='\n'):
        merge = []
        show_type = self.show_type
        for tp, mean in meaning:
            if show_type and tp:
                mean = '[%s] %s'%(tp, mean)
            merge.append(mean)

        return sep.join(merge)

    def random_except(self, seq, excpt):
        if len(seq) == 0:
            return seq[0]
        result = excpt
        while result == excpt:
            result = random.choice(seq)
        return result


    def convert_data(self, data):
        '''parse the source data from `data` to a format:
        data format:
        [{
            "word": ["words", ...],
            "meaning": [["v": "meaning"], ...]
        }, ...]
        return format:
        {
            ("words", ...): (("v", "meaning"), ...)
        }'''

        result = {}
        for each in data:
            result[tuple(each['word'])] = tuple((ty, meaning) for ty, meaning in each['meaning'])

        return result

def main():
    return Game().run()

if __name__ == '__main__':
    getlogger('wordson', logging.DEBUG)
    logging.getLogger('wordson.runline.role').setLevel(logging.CRITICAL)
    main()
